/*
PID算法，使用温度值作为输入
周期时间内，继电器工作的时长作为输出控制参数，单位为ms
工作时间越长，周期时间内加热功率越大，温度上升越快，且温度控制只有加热器控制，因此为正向PID
*/

// #include <IRremote.h>
#include <TaskScheduler.h>
#include <OneWire.h>
#include <DallasTemperature.h>	
#include <PID_v1.h>
#include <U8glib.h> 
#include "DHT.h"

#include "key.h"
#include "zh-cn.h"

// 温湿度检测
#define DHTPIN A6       //温湿度输入
#define DHTTYPE DHT21   // DHT 21 (AM2301)
DHT dht(DHTPIN, DHTTYPE);

#define ONE_WIRE_BUS 8 //ds18b20 数据IO D8 pin
#define TEMPERATURE_PRECISION 9 //ds18b20 分辨率

// #define IR_RECV_PIN 5 // D5 pin 红外IO

#define EC11_CL 3 //旋转编码器中断信号引脚
#define EC11_DT 4 //旋转编码器数据电平引脚
#define EC11_K 9 //旋转编码器按键引脚

#define BEE 6 //蜂鸣器引脚

//显示文字
const unsigned char current_chinese[] U8G_PROGMEM = CURRENT_CN;
const unsigned char set_chinese[] U8G_PROGMEM = SET_CN;
const unsigned char time_chinese[] U8G_PROGMEM = TIME_CN;
const unsigned char minute_chinese[] U8G_PROGMEM = MINUTE_CN;

//DS18B20 单总线最多支持8路，先按6路计算，预计实际使用2~4路取平均值
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress dsAddrArr[6]; //ds18b20 地址存储数组
uint8_t dsCount = 4;  //ds18b20 数量
float dsValueArr[6];  //ds18b20 返回温度值存储数组

//红外接收
// IRrecv irrecv(IR_RECV_PIN);
// decode_results irKey;

//OLED
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
uint8_t redraw_required = 0; //重绘中

uint8_t setting_state = 0; //设置中, 0-正常，1-设定时间，2-设定温度

//PID	
double Setpoint, Input, Output;
// kp 2350 超调后不再震荡，温度稳定27.5-28之间，存在静差，但是可用
double kp = 2400, ki = 0, kd = 0;  //PID参数
PID controlPID(&Input, &Output, &Setpoint, kp, ki, kd, DIRECT);	
uint16_t WindowSize = 5000; //窗口时间，5s 一个波形周期	
uint16_t windowStartTime; //加热时间

//计时
uint8_t timeout = 30; //分钟计时
uint8_t timeState = 0; //是否开始计时

//当前
float current_temperature = 10.0;
uint8_t current_humidity = 50;

//设定
uint8_t set_temperature = 28;
uint8_t set_humidity = 75;

//继电器
uint8_t hotControl = 13;    //D13引脚控制加热
uint8_t humControl = 12;    //D12引脚控制加湿器

uint8_t beeTaskId;

void initDSDevice(){

  sensors.begin();
  
  dsCount = sensors.getDeviceCount(); //获取设备个数

  Serial.print(dsCount, DEC);
  Serial.println(" DS18B20 devices.");

  uint8_t i;
  for (i = 0; i < dsCount; i++){
    sensors.getAddress(dsAddrArr[i], i);
    sensors.setResolution(dsAddrArr[i], TEMPERATURE_PRECISION);
  }
}

//多路温度数据直接输出到串口，并计算所有数据平均值
float getCurrentTmpHum(){
  sensors.requestTemperatures();

  if(dsCount > 0){
    uint8_t i;
    float sum = 0;
    for (i = 0; i < dsCount; i++){
      dsValueArr[i] = sensors.getTempC(dsAddrArr[i]);
      sum += dsValueArr[i];
    }
    current_temperature = sum / dsCount;    
  }

  return current_temperature;
}

void getDHTValue(){
  uint8_t t = uint8_t(dht.readTemperature()); //读取温度，保留整数
  uint8_t h = uint8_t(dht.readHumidity());    //读取湿度，保留整数
  
  if (!isnan(h)) {
    current_humidity = h;
    Serial.println(String("") + "AM2301 T:" + t + ",  H: " + h);
  }  
}

void initPIDParams(){
  // PID控制设定
  controlPID.SetTunings(kp, ki, kd);  

  windowStartTime = millis();
  //Serial.println(windowStartTime);

  Setpoint = set_temperature; //目标值
	
  controlPID.SetOutputLimits(0, WindowSize); //输出控制变量范围

  controlPID.SetSampleTime(1000); //算法采样时间，1s

  controlPID.SetMode(AUTOMATIC); //自动计算

}

//对比数据，目前停用
// void compareValue(){
//   if(current_temperature < set_temperature){
//     digitalWrite(hotControl, HIGH);
//   }else{
//     digitalWrite(hotControl, LOW);
//   }
//   if(current_humidity < set_humidity){
//     digitalWrite(humControl, HIGH);
//   }else{
//     digitalWrite(humControl, LOW);
//   }
// }

//PID计算控制
void PIDCompute(){
  getCurrentTmpHum(); //获取温度

  Input = current_temperature;  //温度值作为PID输入

  controlPID.Compute(); //PID计算

  Serial.print(String("") + "Input: " + Input + ",  Output: " + Output);

  uint16_t time = millis() - windowStartTime; //当前周期中，已经运行的时间

  if(time > WindowSize){ //运行时间 > 周期时间 则进入下一周期，初始化加热状态为加热
    windowStartTime += WindowSize;
    Serial.print(", ");
    Serial.print(windowStartTime);
    time = millis() - windowStartTime;
    digitalWrite(hotControl, HIGH);
  }

  Serial.print(", ");
  Serial.print(time);

  if(Output > time) {
    //PID输出 > 当前已经运行的时间，则执行加热
    digitalWrite(hotControl, HIGH);
    Serial.println(", [+]");
  }else{
    //否则取消加热
    digitalWrite(hotControl, LOW);
    Serial.println(", [-]");
  }

  redraw_required = 1; //数据更新后，触发重绘
}

const char *convert(float val) {
  static char buf[5];
  uint8_t integer = (uint8_t)(val);
  strcpy(buf, u8g_u8toa( integer, 2));
  buf[2] =  '.';
  buf[3] = (val*10 - integer*10) + '0';
  buf[4] = '\0';
  return buf;
}

void draw(void) {  
  uint8_t txtH, posY;
  u8g_uint_t posX;
  if(setting_state == 2){
    u8g.drawLine(20, 60, 50, 60);
  }else if(setting_state == 1){
    u8g.drawLine(80, 60, 105, 60);
  } 
  u8g.drawLine(0, 32, 128, 32);
  u8g.drawXBMP( 1, 2, 16, 28, current_chinese);
  u8g.drawXBMP( 0, 34, 16, 28, set_chinese);
  u8g.drawXBMP( 58, 34, 16, 28, time_chinese);
  u8g.drawXBMP( 108, 34, 16, 28, minute_chinese);
  u8g.setFont(u8g_font_helvR14);
  u8g.setFontRefHeightText();
  u8g.setFontPosTop();
  txtH = u8g.getFontAscent()-u8g.getFontDescent(); //18
  
  char line1[16] = "";
  strcat(line1, convert(current_temperature));
  strcat(line1, "`c  ");
  strcat(line1, u8g_u8toa( current_humidity, 2));
  strcat(line1, "%");

  posX = 16 + (128 - 16 - u8g.getStrWidth(line1))/2;
  posY = (32 - txtH)/2; //7

  u8g.drawStr( posX, posY, line1); 

  char line2[8] = "";
  strcat(line2, u8g_u8toa( set_temperature, 2));
  strcat(line2, "`c");
  u8g.drawStr( 20, posY+32, line2);

  char line3[8] = "";
  strcat(line3, u8g_u8toa( timeout, 3));
  //strcat(line1, " min ");
  u8g.drawStr( 76, posY+32, line3);
}

// void changeSettingValue(int8_t val){
//   if(in_settings){
//     if(setting_temperature){
//       set_temperature = set_temperature + val;
//     }else{
//       set_humidity=set_humidity + val*5;
//     }
//   }
//   redraw_required = 1;
// }
void onkeydown(unsigned long keyCode){
  switch (keyCode){
    case KEY_SET:
      setting_state ++;
      if(setting_state > 2){
        setting_state = 0;
      }
      redraw_required = 1;
      break;
    // case KEY_OK:
    // case KEY_BACK:
    //   break;
    // case KEY_UP:
    //   break;  
    // case KEY_DOWN:
    //   break;
    // case KEY_MENU:
    //   break;
    // default:
    //   break;
  }
}

void timeCutDown(){
  if(setting_state != 1){
    if(timeout > 0){
      timeout--;
      redraw_required = 1;
    }else{
      digitalWrite(BEE, HIGH);
      delay(200);
      digitalWrite(BEE, LOW);
      delay(200);
      digitalWrite(BEE, HIGH);
      delay(200);
      digitalWrite(BEE, LOW);
      delay(1000);
      digitalWrite(BEE, HIGH);
      delay(200);
      digitalWrite(BEE, LOW);
      delay(200);
      digitalWrite(BEE, HIGH);
      delay(200);
      digitalWrite(BEE, LOW);
      timeout = 10;
    }
  }
}

//修改设定温度
void valCount(){
  if(setting_state == 1){
    if(digitalRead(EC11_DT)){
      timeout -= 5;
    }else{
      timeout += 5;
    }
  }else if(setting_state == 2){
    if(digitalRead(EC11_DT)){
      set_temperature --;
    }else{
      set_temperature ++;
    }
    Setpoint = set_temperature;
    Serial.println(String("") + "Setpoint: " + Setpoint);
  }
  
  redraw_required = 1;
}

//检测按键输入
unsigned long keyTime; //按键时间
void btnCheck(){
  if(digitalRead(EC11_K) == 0){
    // Serial.print(millis());
    // Serial.print("-");
    // Serial.println(keyTime);
    if(millis() - keyTime > 200){
      keyTime = millis();
      Serial.println("EC11_K press");
      onkeydown(KEY_SET);
    }
  }
}

void setup(void){
  //继电器动作引脚
  pinMode(hotControl, OUTPUT);
  pinMode(humControl, OUTPUT);
  //蜂鸣器引脚
  pinMode(BEE, OUTPUT);
  digitalWrite(BEE, LOW);
  //编码器引脚
  pinMode(EC11_CL, INPUT_PULLUP); 
  pinMode(EC11_DT, INPUT_PULLUP); 
  pinMode(EC11_K, INPUT_PULLUP); 

  attachInterrupt(digitalPinToInterrupt(EC11_CL), valCount, RISING);

  // irrecv.enableIRIn(); // 红外初始化
  
  Serial.begin(9600); // 串口初始化

  initDSDevice(); //多路ds18b20 初始化

  initPIDParams(); //PID算法初始化设置

  Sch.init();
  Sch.addTask(getDHTValue, 500, 1000 * 10, 1); //从第 500 毫秒开始执行，每隔 10s
  // //Sch.addTask(compareValue, 0, 200, 1); //从第 200 毫秒开始执行，每隔 1s
  Sch.addTask(timeCutDown, 1000, 1000 * 60, 1);

  Sch.addTask(PIDCompute, 0, 500, 1);

  Sch.addTask(btnCheck, 0, 30, 0); //10ms 检测一次按键, 抢占式，优先级更高
  
 //PID计算，从第 0 毫秒开始执行，每隔 500ms
  Sch.start();  // Start the task scheduler

  redraw_required = 1;
}

void loop(void) {

  Sch.dispatchTasks();

  //红外接收
  // if (irrecv.decode(&irKey)) {
  //   Serial.print("IR Key: ");
  //   Serial.println(irKey.value, HEX);
  //   onkeydown(irKey.value);
  //   irrecv.resume(); // Receive the next value
  // }

  if (  redraw_required != 0 ) {
    u8g.firstPage();
    do  {
      draw();
    } while( u8g.nextPage() );
    redraw_required = 0;
  }
}